'''
借鉴文档：
- https://hellowac.github.io/docx-doc-zh/api/text_font_object.html
- https://blog.51cto.com/u_16175436/7454050
- https://cloud.tencent.com/developer/article/2295354
- https://www.jhanmath.com/?p=130
'''
from docx import Document
from docx.text.paragraph import Paragraph
from docx.shared import Pt
from docx.oxml import OxmlElement,ns
from loguru import logger
from json import loads
from sys import stderr,argv
from fire import Fire
#from latex2word import LatexToWordElement
import elements
import os

#脚注列表。sy中的标记会被转化为挖空，答案写在文件的最后面
#由于`python-docx`库暂不支持脚注，只好在最后用正文代替了，参见https://python-docx.readthedocs.io/en/latest/user/documents.html?highlight=footnote#really-opening-a-document
footnotes=[]
def parse_node(node:dict,document:Document,conf:dict,**kwargs):

    if node['Type'] == 'NodeHeading':
        #标题块
        text=elements.force_to_text(node['Children'])
        document.add_heading(text,node['HeadingLevel'])
        logger.trace(f'{node["ID"]}已添加标题：'+text)
    elif node['Type'] == 'NodeParagraph':
        if kwargs.get('marker_type') in (1,2):
            paragraph:Paragraph=document.add_paragraph(style={1:'List Number',2:"List Bullet"}[kwargs['marker_type']])
        else:#无列表或待办列表
            paragraph:Paragraph=document.add_paragraph()
        if not node.get('Children'):return
        for child in node['Children']:
            parse_text(child,document,paragraph,conf)
        logger.trace(f'{node["ID"]}已添加段落')
    elif node['Type'] == 'NodeList':
        if not node.get('Children'):return
        for list_item in node['Children']:
            parse_node(list_item,document,conf)#列表的子项是递归处理
        logger.trace(f'{node["ID"]}已添加列表：')
    elif node['Type'] == 'NodeListItem':
        if not node.get('Children'):return
        for child in node['Children']:
            parse_node(child,document,conf,
                       marker_type=node['ListData'].get('Typ',2),#列表样式。1有序2无序3任务
                       )#列表的子项中，再次递归
        logger.trace(f'{node["ID"]}已添加列表项：')
    elif node['Type'] == 'NodeTaskListItemMarker':
        document.add_paragraph().add_run(conf.get('TaskListItemChecked','[✓]') if node.get('TaskListItemChecked') else conf.get('TaskListItemUnchecked','[   ]') )
    elif node['Type'] == 'NodeWidget':
        logger.warning(f'{node["ID"]}不支持按钮，已跳过')
    elif node['Type'] == 'NodeBlockQueryEmbed':
        logger.warning(f'{node["ID"]}不支持嵌入块，已跳过')
    elif node['Type'] == 'NodeBlockquote':
        #引用。其中Children的第0项只是一个“>”，所以舍掉了
        #TODO:引用块的样式
        parse_node(node['Children'][1],document,conf)
    elif node['Type'] == 'NodeTable':
        elements.add_table(node,document,conf)
    elif node['Type'] == 'NodeCodeBlock':
        data=node['Children'][2]['Data']
        run=document.add_paragraph().add_run(data)
        run.font.emboss=True#TODO:上浮样式
        run.font.name=conf.get('code_font', 'Consolas')
        logger.trace('已添加代码块：'+data)
    elif node['Type'] == 'NodeMathBlock':
        #LatexToWordElement(node['Children'][1]['Data']).add_latex_to_paragraph(document.add_paragraph())
        logger.warning(f'{node["ID"]}不支持数学公式，已跳过')
    elif node['Type'] == 'NodeThematicBreak':#分割线
        if conf.get('split_line_page', False):
            document.add_page_break()
            logger.trace('分割线已被添加为分页符')
    else:
        logger.warning(f'{node["ID"]}未知节点类型，已忽略：'+node['Type'])


def parse_text(text_node:dict,document:Document,paragraph:Paragraph,conf:dict):
    if text_node['Type'] == 'NodeText':
        paragraph.add_run(text_node['Data'])
        logger.trace('已添加文本：'+text_node['Data'])
    elif text_node['Type'] == 'NodeImage':
        href=text_node['Children'][5]['Data']
        if 'assets' in href:#附件，需要处理路径
            href=os.path.join(conf['sy-file-path'],href)
        document.add_picture(href)
    elif text_node['Type'] == 'NodeTextMark':
        if text_node['TextMarkType'] == 'a':#链接
            href:str=text_node['TextMarkAHref']
            if 'assets' in href:#附件，需要处理路径
                href=os.path.join(conf['sy-file-path'],href)
            elements.add_hyperlink(paragraph,text_node['TextMarkTextContent'],href)
            logger.trace('已添加超链接：'+text_node['TextMarkTextContent'])
            #TODO:把文件包括在里面
        elif 'mark' in text_node['TextMarkType']:#标注，转换成Word脚注
            #TODO:标注中的样式
            footnotes.append(text_node['TextMarkTextContent'])#最后由elements.save_footnotes处理
            paragraph.add_run(conf.get('mark_replacement','_'*len(text_node['TextMarkTextContent'])))
            paragraph.add_run(f"[{len(footnotes)}]").font.superscript=True
            logger.trace('已添加标注文本：'+text_node['TextMarkTextContent'])
        elif text_node['TextMarkType'] == 'code':
            run=paragraph.add_run(text_node['TextMarkTextContent'])
            run.font.emboss=True#上浮样式
            run.font.name=conf.get('code_font', 'Consolas')
            logger.trace('已添加代码文本：'+text_node['TextMarkTextContent'])
        elif text_node['TextMarkType'] == 'inline-math':
            logger.trace('暂不支持数学文本：'+text_node['TextMarkInlineMathContent'])
            #LatexToWordElement(text_node['TextMarkInlineMathContent']).add_latex_to_paragraph(paragraph)

        elif text_node['TextMarkType'] == 'kbd' or text_node['TextMarkType'] == 'tag':
            #键盘或标记
            #TODO:把键盘和标记的样式区分开（求建议）
            run=paragraph.add_run(text_node['TextMarkTextContent'])
            run.font.name = 'Consolas'
            run.font.shadow = True
        elif text_node['TextMarkType'] == 'inline-memo':
            footnotes.append(text_node['TextMarkInlineMemoContent'])
            paragraph.add_run(text_node['TextMarkTextContent'])
            paragraph.add_run(f"[{len(footnotes)}]").font.superscript=True
        elif text_node['TextMarkType'] == 'block-ref':
            paragraph.add_run("(("+text_node['TextMarkTextContent'])
            logger.warning(f"引用块暂不支持："+text_node['TextMarkTextContent'])
        else:
            styles=text_node['TextMarkType'].split(' ')
            run=paragraph.add_run(text_node['TextMarkTextContent'])
            if 'strong' in styles:run.bold=True
            if 'em' in styles:run.italic=True
            if 'u' in styles:run.underline=True
            if 's' in styles:run.font.strike=True
            if 'sub' in styles:run.font.subscript=True
            if 'sup' in styles:run.font.superscript=True
            logger.trace(f'已添加样式为{styles}的文本：{text_node["TextMarkTextContent"]}')
    else:
        logger.warning('未知NodeText类型，已忽略：'+text_node['Type'])



def main(i='',o='',conf_path='./conf.json'):

    if not i:
        i=input('请输入文件路径或拖拽文件到本窗口，再按回车：')
    if not o:
        o=os.path.join(os.path.dirname(i),os.path.basename(i).replace('.sy','.docx')+'.docx')
    #读取配置
    try:
        f=open(conf_path,encoding='utf-8')
        conf=loads(f.read())
    except:
        conf={}


    #日志记录器
    if conf.get('log_level'):
        logger.remove(0)
        logger.add(stderr,level=conf.get('log_level'))
    if conf.get('log_path'):
        logger.add(conf.get('log_path'),conf.get('log_level','DEBUG'))

    logger.info('sy2docx启动')

    logger.info('开始处理文档：'+i)
    try:
        f=open(i,encoding='utf-8')
        sy=loads(f.read())
    except FileNotFoundError:
        logger.error('找不到文件：'+i)
        return
    logger.info(f"文档标题：{sy['Properties']['title']}，文档ID：{sy['ID']}")
    logger.info('修改日期：'+sy['Properties']['updated'])

    document=Document()
    footnotes.clear()#清空脚注

    #获取sy文件的目录
    conf['sy-file-path']=os.path.dirname(i)

    if conf.get('title_to_head0',True):
        document.add_heading(sy['Properties']['title'],0)
    
    #设置中文字体
    style = document.styles['Normal']
    style.font.name = 'Times New Roman' # 必须先设置font.name
    style.font.size = Pt(14)
    style.element.rPr.rFonts.set(ns.qn('w:eastAsia'), '宋体')


    for child in sy['Children']:
        parse_node(child,document,conf)
    
    elements.save_footnotes(footnotes,document,conf)

    document.save(o)
    os.system('pause')

if len(argv)>1:
    #拖拽
    main(argv[1],os.path.join(os.path.dirname(argv[1]),os.path.basename(argv[1]).replace('.sy','.docx')+'.docx'))
else:
    #命令行模式
    Fire(main)

    


